/**
 * This file is part of 1genia trampoline
 * Copyright (C) 2007 1genia (contact@1genia.com)
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; version 3 of the License. 
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details. 
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; see the file COPYING.TXT.  If not,
 * write to the Free Software Foundation, Inc., 51 Franklin Street,
 * Fifth Floor, Boston, MA 02110-1301, USA. 
 **/
package com.genia.toolbox.persistence.memory.criteria.criterion;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;

import com.genia.toolbox.persistence.basis.criteria.BasicCriteriaResult;
import com.genia.toolbox.persistence.basis.manager.AbstractPersistenceManager;
import com.genia.toolbox.persistence.exception.AggregateException;
import com.genia.toolbox.persistence.exception.FieldDefinitionException;
import com.genia.toolbox.persistence.exception.PersistenceException;
import com.genia.toolbox.persistence.memory.criteria.MemoryCriteria;
import com.genia.toolbox.persistence.memory.criteria.field.AbstractMemoryField;

/**
 * This criterion represent an inclusion between a field and a collection.
 */
public class MemoryIsInCriterion
    implements MemoryCriterion
{

  /**
   * this interface represents a builder for the collection.
   */
  private interface Collectionable
  {
    /**
     * build the collection.
     * 
     * @param persistenceManager
     *          the implementation of <code>PersistenceManager</code>
     * @param line
     *          the line to test
     * @return the collection to test
     * @throws FieldDefinitionException
     *           when there is an error in the definition of a field
     * @throws PersistenceException
     *           when a persistence error occurred
     */
    public Collection<?> getCollection(AbstractPersistenceManager persistenceManager, BasicCriteriaResult<?> line)
        throws FieldDefinitionException, PersistenceException;
  }

  /**
   * This class represents a <code>Collectionable</code> wrapping a
   * <code>Collection</code>.
   */
  private class SimpleCollectionable
      implements Collectionable
  {
    /**
     * the <code>Collection</code> wrapped.
     */
    private final transient Collection<?> collection;



    /**
     * Constructor.
     * 
     * @param collection
     *          the <code>Collection</code> wrapped
     */
    public SimpleCollectionable(final Collection<?> collection)
    {
      this.collection = collection;
    }



    /**
     * build the collection.
     * 
     * @param persistenceManager
     *          the implementation of <code>PersistenceManager</code>
     * @param line
     *          the line to test
     * @return the collection to test
     */
    public Collection<?> getCollection(final AbstractPersistenceManager persistenceManager, final BasicCriteriaResult<?> line)
    {
      return collection;
    }
  }

  /**
   * This class represents a <code>Collectionable</code> wrapping a
   * <code>Field</code>.
   */
  private class FieldCollectionable
      implements Collectionable
  {
    /**
     * the <code>Field</code> wrapped.
     */
    private final transient AbstractMemoryField field;



    /**
     * Constructor.
     * 
     * @param field
     *          the <code>Field</code> wrapped
     */
    public FieldCollectionable(final AbstractMemoryField field)
    {
      this.field = field;
    }



    /**
     * build the collection.
     * 
     * @param persistenceManager
     *          the implementation of <code>PersistenceManager</code>
     * @param line
     *          the line to test
     * @return the collection to test
     * @throws FieldDefinitionException
     *           when there is an error in the definition of a field
     * @throws AggregateException
     *           when there is an error with an aggregate definition
     */
    public Collection<?> getCollection(final AbstractPersistenceManager persistenceManager, final BasicCriteriaResult<?> line)
        throws FieldDefinitionException, AggregateException
    {
      final Object value = field.getSimpleValue(line);
      if (value instanceof Collection) {
        return (Collection<?>) value;
      }
      if (value instanceof Map) {
        return ((Map<?, ?>) value).values();
      }
      final ArrayList<Object> res = new ArrayList<Object>();
      res.add(value);
      return res;
    }
  }

  /**
   * This class represents a <code>Collectionable</code> wrapping a
   * <code>Criteria</code>.
   */
  private class SubQueryCollectionable
      implements Collectionable
  {
    /**
     * the wrapped <code>Criteria</code>.
     */
    private final transient MemoryCriteria<?> criteria;



    /**
     * Constructor.
     * 
     * @param criteria
     *          the wrapped <code>Criteria</code>
     */
    public SubQueryCollectionable(final MemoryCriteria<?> criteria)
    {
      this.criteria = criteria;
    }



    /**
     * build the collection.
     * 
     * @param persistenceManager
     *          the implementation of <code>PersistenceManager</code>
     * @param line
     *          the line to test
     * @return the collection to test
     * @throws PersistenceException
     *           when a persistence error occurred
     */
    public Collection<?> getCollection(final AbstractPersistenceManager persistenceManager, final BasicCriteriaResult<?> line)
        throws PersistenceException
    {
      return persistenceManager.simpleFind(criteria);
    }

  }

  /**
   * the first operand.
   */
  private final transient AbstractMemoryField field;

  /**
   * the <code>Collectionable</code> that build the collection that will be
   * searched.
   */
  private final transient Collectionable collectionable;



  /**
   * constructor.
   * 
   * @param field
   *          the first operand
   * @param collection
   *          the collection to search in
   */
  public MemoryIsInCriterion(final AbstractMemoryField field, final Collection<?> collection)
  {
    this.field = field;
    this.collectionable = new SimpleCollectionable(collection);
  }



  /**
   * constructor.
   * 
   * @param field
   *          the first operand
   * @param containerField
   *          the field to search in
   */
  public MemoryIsInCriterion(final AbstractMemoryField field, final AbstractMemoryField containerField)
  {
    this.field = field;
    this.collectionable = new FieldCollectionable(containerField);
  }



  /**
   * constructor.
   * 
   * @param field
   *          the first operand
   * @param criteria
   *          the sub-query to search in
   */
  public MemoryIsInCriterion(final AbstractMemoryField field, final MemoryCriteria<?> criteria)
  {
    this.field = field;
    this.collectionable = new SubQueryCollectionable(criteria);
  }



  /**
   * return <code>true</code> if the <code>BasicCriteriaResult</code> given
   * in parameter match this <code>Criterion</code> in the
   * <code>Criteria> criteria</code>.
   * 
   * @param persistenceManager
   *          the implementation of <code>PersistenceManager</code>
   * @param criteria
   *          the criteria this <code>Criterion</code> comes from
   * @param line
   *          the line to test
   * @return if the <code>BasicCriteriaResult</code> given in parameter match
   *         this <code>Criterion</code>.
   * @throws PersistenceException
   *           when a persistence error occurred
   */
  public boolean match(final AbstractPersistenceManager persistenceManager, final MemoryCriteria<?> criteria, final BasicCriteriaResult<?> line)
      throws PersistenceException
  {
    final Collection<?> container = collectionable.getCollection(persistenceManager, line);
    return container.contains(field.getSimpleValue(line));
  }



  /**
   * Creates and returns a deep copy of this object.
   * 
   * @return a deep copy of this object
   */
  @Override
  public MemoryIsInCriterion clone()
  {
    return this;
  }
}
